package org.fastsyncer.parser.ldap;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentLinkedQueue;

import javax.naming.NamingEnumeration;
import javax.naming.directory.Attributes;
import javax.naming.directory.SearchControls;

import org.fastsyncer.common.constant.ConnectorConstant;
import org.fastsyncer.common.entity.LdapConfig;
import org.fastsyncer.common.entity.Mapping;
import org.fastsyncer.common.entity.MappingTask;
import org.fastsyncer.connector.common.LdapConstant;
import org.fastsyncer.connector.framework.ConnectorFactory;
import org.fastsyncer.connector.ldap.LdapConnector;
import org.fastsyncer.parser.util.ConvertUtil;
import org.json.JSONArray;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ldap.NamingException;
import org.springframework.ldap.core.AttributesMapper;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.core.LdapTemplate;

public class LdapParser {

    // 实现单例模式的方法，通过方法来实现实例化
    // 声明一个私有的静态属性，使只被实例化一次
    private static final LdapParser instance = new LdapParser();

    // 声明一个私有的构造方法，使别人不能随便new对象
    // 它只被调用一次，实例存在之后就不再调用了
    private LdapParser() {
    }

    // 声明一个公共的静态方法，外界通过调用这个方法来得到唯一的实例
    public static LdapParser getInstance() {
        return instance;
    }

    private static Logger logger = LoggerFactory.getLogger(LdapParser.class);

    /**
     * 解析数据源ldap类型
     * 
     * @param mappingTask
     * @return
     * @throws Exception
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public List<Map<String, Object>> parseSource(MappingTask mappingTask) throws Exception {
        Mapping sourceMapping = mappingTask.getSourceMapping();
        LdapConnector conn = (LdapConnector) ConnectorFactory.getInstance(sourceMapping.getConnector());
        // 数据源实例
        LdapConfig sourceConfig = (LdapConfig) sourceMapping.getConfig();
        LdapTemplate ldapTemplate = conn.getLdapTemplate(sourceConfig);

        // 转换器
        AttributesMapper mapper = new AttributesMapper() {
            public Object mapFromAttributes(Attributes attrs) throws NamingException, javax.naming.NamingException {
                Map<String, Object> r = new HashMap<String, Object>();
                NamingEnumeration<String> iDs = attrs.getIDs();
                while (iDs.hasMoreElements()) {
                    String key = (String) iDs.nextElement();
                    Object value = attrs.get(key).get();
                    r.put(key, value);
                }
                return r;
            }
        };

        /**
         * 期望返回的数据格式: [ {ou=scdl, objectClass=top, description=四川电力,
         * homeDirectory=ou=scdl_part1,ou=scdl,dc=aostar,dc=com, cn=sss}, ... ]
         */
        String filter = sourceMapping.getExcuteCommond().get(ConnectorConstant.OPERTION_QUERY);
        /**
         * 0:OBJECT_SCOPE,搜索指定的命名对象。 <br />
         * 1:ONELEVEL_SCOPE,只搜索指定命名对象的一个级别，这是缺省值。<br />
         * 2:SUBTREE_SCOPE,搜索以指定命名对象为根结点的整棵树,就像递归查询
         **/
        SearchControls controls = new SearchControls();
        // 查询方式
        Integer searchScope = LdapConstant.OPERS.get(sourceConfig.getSearchScope());
        controls.setSearchScope(searchScope);
        List<Map<String, Object>> search = ldapTemplate.search("", filter, controls, mapper);
        return search;
    }

    /**
     * 同步数据源ldap类型
     * 
     * @Title: syncTarget
     * @Description: 同步数据源ldap类型
     * @param mappingTask
     *            映射关系
     * @param rows
     *            同步的数据
     * @param pos
     *            开始位置和结束位置
     * @param reasonQuene
     *            错误消息队列，用于记录发生的错误日志
     * @throws Exception
     * @return: Integer[] 总数和成功数
     */
    public Integer[] syncTarget(MappingTask mappingTask, List<Map<String, Object>> rows, Integer[] pos, ConcurrentLinkedQueue<JSONObject> reasonQuene) throws Exception {
        Mapping targetMapping = mappingTask.getTargetMapping();

        // 获取数据源、目标源同步的属性
        List<HashMap<String, Object>> targetFileds = targetMapping.getFileds();
        // 检查同步属性
        int targetSize = targetFileds.size();
        if (targetSize < 0) {
            throw new Exception("同步的属性不存在，请检查!");
        }

        // 获取目标源实例
        LdapConfig targetConfig = (LdapConfig) targetMapping.getConfig();
        String tDn = targetConfig.getDn();
        // 目标源继承对象
        Object[] textendObjects = targetConfig.getExtendObjects().toArray();

        // 2.0版本推荐:DirContextAdapter是使用LDAP属性非常有用的工具，特别是添加或修改数据时。
        LdapConnector conn = (LdapConnector) ConnectorFactory.getInstance(targetMapping.getConnector());
        LdapTemplate targetTemplate = conn.getLdapTemplate(targetConfig);
        DirContextAdapter ctx = null;
        // 总数和成功数
        int total = 0;
        int success = 0;
        for (Map<String, Object> map : rows) {
            try {
                // 设置数据DN
                ctx = new DirContextAdapter(map.get(tDn).toString());
                // 设置属性
                for (Map.Entry<String, Object> row : map.entrySet()) {
                    ctx.addAttributeValue(row.getKey(), row.getValue());
                }
                ctx.setAttributeValues("objectclass", textendObjects);
                targetTemplate.bind(ctx);
                success++;
            } catch (Exception e) {
                // 记录错误信息
                JSONObject err = new JSONObject();
                if (pos != null && pos.length == 2) {
                    err.put("startPos", pos[0]);
                    err.put("endPos", pos[1]);
                }
                err.put("cause", e.getCause());
                err.put("message", e.getMessage());
                reasonQuene.offer(err);
            } finally {
                total++;
            }
        }
        return new Integer[] { total, success };
    }

    /**
     * 增量同步新增到目标源
     * 
     * @param mappingTask
     * @param base 域
     * @param after 增量数据
     * @return true/false
     * @throws Exception
     */
    public boolean insert(MappingTask mappingTask, String base, JSONArray after) throws Exception {
        logger.info(after.toString());
        // 1.解析数据源
        Mapping sourceMapping = mappingTask.getSourceMapping();
        Mapping targetMapping = mappingTask.getTargetMapping();

        // 2.对数据进行转换处理
        Map<String, Object> row = ConvertUtil.convert(sourceMapping, targetMapping, after);

        // 执行新增
        LdapConnector conn = (LdapConnector) ConnectorFactory.getInstance(targetMapping.getConnector());
        LdapConfig config = (LdapConfig) targetMapping.getConfig();
        String dn = config.getDn();
        LdapTemplate ldapTemplate = conn.getLdapTemplate(config);

        // 2.0版本推荐:DirContextAdapter是使用LDAP属性非常有用的工具，特别是添加或修改数据时。
        // 设置域 cn=yangp,ou=scdl,dc=aostar,dc=com
        DirContextAdapter ctx = new DirContextAdapter(base);
        // 继承对象
        Object[] extendObjects = config.getExtendObjects().toArray();
        ctx.setAttributeValues("objectclass", extendObjects);

        // 转换基本属性名称和值
        for (Map.Entry<String, Object> obj : row.entrySet()) {
            // 排除dn
            String key = obj.getKey();
            if (dn.equals(key)) {
                continue;
            }
            // 目标源名称
            ctx.setAttributeValue(key, obj.getValue());
        }

        // 绑定
        ldapTemplate.bind(ctx);
        return true;
    }

    /**
     * 增量同步修改到目标源
     * 
     * @param mappingTask
     * @param base 域
     * @param after 增量数据
     * @return true/false
     * @throws Exception
     */
    public boolean update(MappingTask mappingTask, String base, JSONArray after) throws Exception {
        logger.info(after.toString());
        // 1.解析数据源
        Mapping sourceMapping = mappingTask.getSourceMapping();
        Mapping targetMapping = mappingTask.getTargetMapping();

        // 2.对数据进行转换处理
        Map<String, Object> row = ConvertUtil.convert(sourceMapping, targetMapping, after);

        // 执行修改
        LdapConnector conn = (LdapConnector) ConnectorFactory.getInstance(targetMapping.getConnector());
        LdapConfig config = (LdapConfig) targetMapping.getConfig();
        String dn = config.getDn();
        LdapTemplate ldapTemplate = conn.getLdapTemplate(config);

        // 2.0版本推荐:DirContextAdapter是使用LDAP属性非常有用的工具，特别是添加或修改数据时。
        // 设置域 cn=yangp,ou=scdl,dc=aostar,dc=com
        DirContextAdapter ctx = (DirContextAdapter) ldapTemplate.lookupContext(base);
        // 继承对象
        Object[] extendObjects = config.getExtendObjects().toArray();
        ctx.setAttributeValues("objectclass", extendObjects);

        // 转换基本属性名称和值
        for (Map.Entry<String, Object> obj : row.entrySet()) {
            // 排除dn
            String key = obj.getKey();
            if (dn.equals(key)) {
                continue;
            }
            // 目标源名称
            ctx.setAttributeValue(key, obj.getValue());
        }

        // 修改属性
        ldapTemplate.modifyAttributes(ctx);
        return true;
    }

    /**
     * 增量同步删除到目标源
     * 
     * @param mappingTask
     * @param base 域
     * @param before 增量数据
     * @return true/false
     * @throws Exception
     */
    public boolean delete(MappingTask mappingTask, String base, JSONArray before) throws Exception {
        logger.info(before.toString());
        Mapping targetMapping = mappingTask.getTargetMapping();
        LdapConnector conn = (LdapConnector) ConnectorFactory.getInstance(targetMapping.getConnector());
        LdapConfig config = (LdapConfig) targetMapping.getConfig();
        LdapTemplate ldapTemplate = conn.getLdapTemplate(config);
        // 解除绑定
        ldapTemplate.unbind(base);
        return true;
    }

}
